package com.dodola.animview.library;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.agp.utils.Point;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;

import java.util.ArrayList;

/**
 * Created by dodola on 15/7/27.
 */
public class MetaballView extends Component implements Component.DrawTask {
    /**
     * 默认参数
     */
    public static final int MEASURED_STATE_TOO_SMALL = 0x01000000;

    /**
     * 默认参数
     */
    public static final int MEASURED_STATE_MASK = 0xff000000;
    private Paint paint = new Paint();
    private float handle_len_rate = 2f;
    private float radius = 30;
    private final int ITEM_COUNT = 6;
    private final int ITEM_DIVIDER = 60;
    private final float SCALE_RATE = 0.3f;
    private float maxLength;
    private ArrayList<Circle> circlePaths = new ArrayList<>();
    private float mInterpolatedTime;
    private AnimatorValue wa;
    private Circle circle;

    public MetaballView(Context context) {
        super(context);
        addDrawTask(this);
        init();
    }

    public MetaballView(Context context, AttrSet attrs) {
        super(context, attrs);
        addDrawTask(this);
        init();
    }

    public MetaballView(Context context, AttrSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        addDrawTask(this);
        init();
    }

    private class Circle {
        float[] center;
        float radius;
    }

    /**
     * 设置模式
     *
     * @param mode 模式：1 代表实体，0代表阴影
     */
    public void setPaintMode(int mode) {
        paint.setStyle(mode == 0 ? Paint.Style.STROKE_STYLE : Paint.Style.FILL_STYLE);
        invalidate();
    }

    private void init() {
        paint.setColor(new Color(0xff4db9ff));
        paint.setStyle(Paint.Style.FILL_STYLE);
        paint.setAntiAlias(true);
        Circle circlePath = new Circle();
        circlePath.center = new float[]{(radius + ITEM_DIVIDER), radius * (1f + SCALE_RATE)};
        circlePath.radius = radius / 4 * 3;
        circlePaths.add(circlePath);

        for (int i = 1; i < ITEM_COUNT; i++) {
            circlePath = new Circle();
            circlePath.center = new float[]{(radius * 2 + ITEM_DIVIDER) * i, radius * (1f + SCALE_RATE)};
            circlePath.radius = radius;
            circlePaths.add(circlePath);
        }
        maxLength = (radius * 2 + ITEM_DIVIDER) * ITEM_COUNT;

        startAnimation();
    }

    private float[] getVector(float radians, float length) {
        float x1 = (float) (Math.cos(radians) * length);
        float y1 = (float) (Math.sin(radians) * length);
        return new float[]{
                x1, y1
        };
    }

    /**
     * 画球
     *
     * @param canvas          画布
     * @param js              循环
     * @param is              0
     * @param vs              控制两个圆连接时候长度，间接控制连接线的粗细，该值为1的时候连接线为直线
     * @param handle_len_rate 曲线控制点长度比率
     * @param maxDistance     最大距离
     */
    private void metaball(Canvas canvas, int js, int is, float vs, float handle_len_rate, float maxDistance) {
        final Circle circle1 = circlePaths.get(is);
        final Circle circle2 = circlePaths.get(js);

        RectFloat ball1 = new RectFloat();
        ball1.left = circle1.center[0] - circle1.radius;
        ball1.top = circle1.center[1] - circle1.radius;
        ball1.right = ball1.left + circle1.radius * 2;
        ball1.bottom = ball1.top + circle1.radius * 2;

        RectFloat ball2 = new RectFloat();
        ball2.left = circle2.center[0] - circle2.radius;
        ball2.top = circle2.center[1] - circle2.radius;
        ball2.right = ball2.left + circle2.radius * 2;
        ball2.bottom = ball2.top + circle2.radius * 2;

        float[] center1 = new float[]{

                ball1.getCenter().getPointX(),
                ball1.getCenter().getPointY()

        };
        float[] center2 = new float[]{
                ball2.getCenter().getPointX(),
                ball2.getCenter().getPointY()
        };
        float ds = getDistance(center1, center2);

        float radius1 = ball1.getWidth() / 2;
        float radius2 = ball2.getWidth() / 2;
        float pi2 = (float) (Math.PI / 2);
        float u1;
        float u2;

        if (ds > maxDistance) {
            canvas.drawCircle(ball2.getCenter().getPointX(), ball2.getCenter().getPointY(), circle2.radius, paint);
        } else {
            float scale2 = 1 + SCALE_RATE * (1 - ds / maxDistance);
            float scale1 = 1 - SCALE_RATE * (1 - ds / maxDistance);
            radius2 *= scale2;
            radius1 *= scale1;
            canvas.drawCircle(ball2.getCenter().getPointX(), ball2.getCenter().getPointY(), radius2, paint);
        }

        if (radius1 == 0 || radius2 == 0) {
            return;
        }

        if (ds > maxDistance || ds <= Math.abs(radius1 - radius2)) {
            return;
        } else if (ds < radius1 + radius2) {
            u1 = (float) Math.acos((radius1 * radius1 + ds * ds - radius2 * radius2) /
                    (2 * radius1 * ds));
            u2 = (float) Math.acos((radius2 * radius2 + ds * ds - radius1 * radius1) /
                    (2 * radius2 * ds));
        } else {
            u1 = 0;
            u2 = 0;
        }
        float[] centermin = new float[]{center2[0] - center1[0], center2[1] - center1[1]};

        float angle1 = (float) Math.atan2(centermin[1], centermin[0]);
        float angle2 = (float) Math.acos((radius1 - radius2) / ds);
        float angle1a = angle1 + u1 + (angle2 - u1) * vs;
        float angle1b = angle1 - u1 - (angle2 - u1) * vs;
        float angle2a = (float) (angle1 + Math.PI - u2 - (Math.PI - u2 - angle2) * vs);
        float angle2b = (float) (angle1 - Math.PI + u2 + (Math.PI - u2 - angle2) * vs);

        float[] p1a1 = getVector(angle1a, radius1);
        float[] p1b1 = getVector(angle1b, radius1);
        float[] p2a1 = getVector(angle2a, radius2);
        float[] p2b1 = getVector(angle2b, radius2);

        float[] p1a = new float[]{p1a1[0] + center1[0], p1a1[1] + center1[1]};
        float[] p1b = new float[]{p1b1[0] + center1[0], p1b1[1] + center1[1]};
        float[] p2a = new float[]{p2a1[0] + center2[0], p2a1[1] + center2[1]};
        float[] p2b = new float[]{p2b1[0] + center2[0], p2b1[1] + center2[1]};

        float[] p1Andp2 = new float[]{p1a[0] - p2a[0], p1a[1] - p2a[1]};

        float totalRadius = (radius1 + radius2);
        float d2 = Math.min(vs * handle_len_rate, getLength(p1Andp2) / totalRadius);
        d2 *= Math.min(1, ds * 2 / (radius1 + radius2));
        radius1 *= d2;
        radius2 *= d2;

        float[] sp1 = getVector(angle1a - pi2, radius1);
        float[] sp2 = getVector(angle2a + pi2, radius2);
        float[] sp3 = getVector(angle2b - pi2, radius2);
        float[] sp4 = getVector(angle1b + pi2, radius1);

        Path path1 = new Path();
        path1.moveTo(p1a[0], p1a[1]);

        path1.cubicTo(new Point(p1a[0] + sp1[0], p1a[1] + sp1[1]),
                new Point(p2a[0] + sp2[0], p2a[1] + sp2[1]), new Point(p2a[0], p2a[1]));
        path1.lineTo(p2b[0], p2b[1]);
        path1.cubicTo(new Point(p2b[0] + sp3[0], p2b[1] + sp3[1]),
                new Point(p1b[0] + sp4[0], p1b[1] + sp4[1]), new Point(p1b[0], p1b[1]));
        path1.lineTo(p1a[0], p1a[1]);
        path1.close();
        canvas.drawPath(path1, paint);
    }

    private float getLength(float[] b1) {
        return (float) Math.sqrt(b1[0] * b1[0] + b1[1] * b1[1]);
    }

    private float getDistance(float[] b1, float[] b2) {
        float x1 = b1[0] - b2[0];
        float y1 = b1[1] - b2[1];
        float d1 = x1 * x1 + y1 * y1;
        return (float) Math.sqrt(d1);
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        circle = circlePaths.get(0);
        circle.center[0] = maxLength * mInterpolatedTime;

        RectFloat ball1 = new RectFloat();
        ball1.left = circle.center[0] - circle.radius;
        ball1.top = circle.center[1] - circle.radius;
        ball1.right = ball1.left + circle.radius * 2;
        ball1.bottom = ball1.top + circle.radius * 2;

        canvas.drawCircle(ball1.getCenter().getPointX(), ball1.getCenter().getPointY(), circle.radius, paint);

        for (int i = 1, l = circlePaths.size(); i < l; i++) {
            metaball(canvas, i, 0, 0.6f, handle_len_rate, radius * 4f);
        }
    }



    protected boolean onEstimateSize(int widthEstimateConfig, int heightEstimateConfig) {
        setEstimatedSize(resolveSizeAndState((int) (ITEM_COUNT * (radius * 2 + ITEM_DIVIDER)), widthEstimateConfig, 0),
                resolveSizeAndState((int) (2 * radius * 1.4f), heightEstimateConfig, 0));

        return true;
    }

    /**
     * 转换大小参数
     *
     * @param size               大小
     * @param measureSpec        测量
     * @param childMeasuredState 子类测量状态
     * @return 转换后的大小
     */
    public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
        final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);
        final int result;
        switch (specMode) {
            case MeasureSpec.NOT_EXCEED:
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.PRECISE:
                result = specSize;
                break;
            case MeasureSpec.UNCONSTRAINT:
            default:
                result = size;
        }
        return result | (childMeasuredState & MEASURED_STATE_MASK);
    }

    private void stopAnimation() {
        wa.cancel();
        invalidate();
    }


    private void startAnimation() {
        wa = new AnimatorValue();
        wa.setDuration(12000);
        wa.setLoopedCount(Animator.INFINITE);
        wa.setCurveType(Animator.CurveType.CYCLE);

        wa.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float vs) {
                if (vs < 0) {
                    mInterpolatedTime = -vs;
                } else {
                    mInterpolatedTime = vs;
                }

                invalidate();
            }
        });
        wa.start();
    }


}
